// SPDX-License-Identifier:	GPL-2.0+
#include <common.h>
#include <dm.h>
#include <clk.h>
#include <clk-uclass.h>
#include <asm/io.h>
#include <div64.h>

#include "ls7a_init_lib.h"
#include "ls7a_gmem_config.h"

#define WAIT_HT_UP   0x0
#define WAIT_HT_DOWN 0x1

#ifdef DEBUG
#define ls7a_debug printf
#else
#define ls7a_debug(fmt, args...)
#endif

int CheckHtLock(u64 Ht1CtrlConfAddr)
{
	u32 Val32;

#ifdef CHECK_HT_PLL_LOCK
	Val32 = Readl(Ht1CtrlConfAddr + LS3A_HT_PLL_CONF) >> 3;
	if (!(Val32 & 0x1)) {
		ls7a_debug("Error: CPU HT PLL not locked!!!\n");
		return -1;
	}
#ifdef CHECK_7A_HT_PLL_LOCK
	if (!((Readl(Ht1CtrlConfAddr + 0x3000000 + 0x1f4) >> 3) & 0x1)) {
		ls7a_debug("Error: 7A HT PLL not locked!!!\n");
		return -1;
	}
#endif
	ls7a_debug("PLL check success.\r\n");
#endif
	return 0;
}

int WaitHtBus(u16 Opt, u64 Ht1CtrlConfAddr)
{
	int i;

	if (Opt == 0x0) {
		// Wait Ht Bus to be up
		do {
			ls7a_debug(">");
		} while ((Readl(Ht1CtrlConfAddr + 0x44) & 0x20) == 0);
		ls7a_debug(" 0x%x\r\n", Readl(Ht1CtrlConfAddr + 0x44));
	} else if (Opt == 0x1) {
		// Wait Ht Bus to be down
		for (i=0; i<32; i++) {
			ls7a_debug(">");
			if ((Readl(Ht1CtrlConfAddr + 0x44) & 0x20) == 0)
				break;
		}
		if (i == 32) {
			printf("Wait HT bus down fail!\r\n");
			return -1;
		}
		ls7a_debug("\r\n 0x%x\r\n", Readl(Ht1CtrlConfAddr + 0x44));
	}

	return 0;
}

void ResetBus(u64 BusAddr)
{
	u8 Val = Readb(BusAddr + 0x3e);
	mdelay(100);
	Writeb(BusAddr + 0x3e, Val);
}

int ResetHtLink(u32 LinkNodeNum)
{
	int Status = 0;
	u64 Ht1CtrlConfAddr;
	u64 HtConfAddr;
	u64 Node0Ht13aConfAddr = 0x90000efdfb000000;
	u32 Val32;
	u64 i, j;

#ifdef LS7A_2WAY_CONNECT
	u64 Node1Ht13aConfAddr = 0x90001efdfb000000;

	//reset_node 1 ht1 lo_bus
	ls7a_debug("Reset Node 1 HT1low bus\r\n");
	Val32 = Readb(Node1Ht13aConfAddr + 0x3e) | 0x40;
	Writeb(Node1Ht13aConfAddr + 0x3e, Val32);
	ls7a_debug("after 0x%x\r\n", Readl(Node1Ht13aConfAddr + 0x3c));
	ls7a_debug("Wait HT bus down.");
	if ((Status = WaitHtBus(WAIT_HT_DOWN, Node1Ht13aConfAddr))) {
		return Status;
	}
#endif
	//reset_node 0 ht1 lo_bus
	ls7a_debug("Reset Node 0 HT1low bus\r\n");
	Val32 = Readb(Node0Ht13aConfAddr + 0x3e) | 0x40;
	Writeb(Node0Ht13aConfAddr + 0x3e, Val32);
	ls7a_debug("after 0x%x\r\n", Readl(Node0Ht13aConfAddr + 0x3c));
	ls7a_debug("Wait HT bus down.");
	if ((Status = WaitHtBus(WAIT_HT_DOWN, Node0Ht13aConfAddr))) {
		return Status;
	}
#if defined(QUAD_CPU) && defined(ENABLE_X_LINK)
	u64 Node0Ht1HiConfAddr = (u64)0x90000ffdfb000000;
	u64 Node1Ht1HiConfAddr = (u64)0x90001ffdfb000000;
	u64 Node2Ht1HiConfAddr = (u64)0x90002ffdfb000000;
	u64 Node3Ht1HiConfAddr = (u64)0x90003ffdfb000000;

	ls7a_debug("Dereset Node 0 HT1 Hi bus\r\n");
	Val32 = Readb(Node0Ht1HiConfAddr + 0x3e) & ~0x40;
	Writeb(Node0Ht1HiConfAddr + 0x3e, Val32);
	ls7a_debug("after 0x%x\r\n", Readl(Node0Ht1HiConfAddr + 0x3c));

	ls7a_debug("Dereset Node 1 HT1 Hi bus\r\n");
	Val32 = Readb(Node1Ht1HiConfAddr + 0x3e) & ~0x40;
	Writeb(Node1Ht1HiConfAddr + 0x3e, Val32);
	ls7a_debug("after 0x%x\r\n", Readl(Node1Ht1HiConfAddr + 0x3c));

#if 1//whd: Check if CRC error bit set and reset it
check_NODE0_HI:
	ls7a_debug("Checking Node 0 HT1 hi CRC error.\r\n");

	do {
		ls7a_debug(">");
	} while((Readl(Node0Ht1HiConfAddr + 0x44) & 0x20) == 0);

	Val32 = Readl(Node0Ht1HiConfAddr + 0x44) & 0xfffffcff;
	Writel(Node0Ht1HiConfAddr + 0x44, Val32);
	mdelay(100);

	for (i=0x200; i>0; i--) {
		if ((Readl(Node0Ht1HiConfAddr + 0x44) & 0x300) == 0) {
			continue;
		} else {
			printf("CRC error found.\r\n");
			printf(" 0x%x\r\n", Readl(Node0Ht1HiConfAddr + 0x44));
			ResetBus(Node0Ht1HiConfAddr);
			goto check_NODE0_HI;
		}
	}

	ls7a_debug("Checking Node 3 HT1 hi CRC error.\r\n");

	do {
		ls7a_debug(">");
	} while ((Readl(Node3Ht1HiConfAddr + 0x44) & 0x20) == 0);

	Val32 = Readl(Node3Ht1HiConfAddr + 0x44) & 0xfffffcff;
	Writel(Node3Ht1HiConfAddr + 0x44, Val32);
	mdelay(100);

	for (i=0x200; i>0; i--) {
		if ((Readl(Node3Ht1HiConfAddr + 0x44) & 0x300) == 0) {
			continue;
		} else {
			printf("CRC error found.\r\n");
			printf(" 0x%x\r\n", Readl(Node3Ht1HiConfAddr + 0x44));
			ResetBus(Node3Ht1HiConfAddr);
			goto check_NODE0_HI;
		}
	}

check_NODE1_HI:
	ls7a_debug("Checking Node 1 HT1 hi CRC error.\r\n");

	do {
		ls7a_debug(">");
	} while ((Readl(Node1Ht1HiConfAddr + 0x44) & 0x20) == 0);

	Val32 = Readl(Node1Ht1HiConfAddr + 0x44) & 0xfffffcff;
	Writel(Node1Ht1HiConfAddr + 0x44, Val32);
	mdelay(100);

	for (i=0x200; i>0; i--) {
		if ((Readl(Node1Ht1HiConfAddr + 0x44) & 0x300) == 0) {
			continue;
		} else {
			printf("CRC error found.\r\n");
			printf(" 0x%x\r\n", Readl(Node1Ht1HiConfAddr + 0x44));
			ResetBus(Node1Ht1HiConfAddr);
			goto check_NODE1_HI;
		}
	}

	ls7a_debug("Checking Node 2 HT1 hi CRC error.\r\n");

	do {
		ls7a_debug(">");
	} while ((Readl(Node2Ht1HiConfAddr + 0x44) & 0x20) == 0);

	Val32 = Readl(Node2Ht1HiConfAddr + 0x44) & 0xfffffcff;
	Writel(Node2Ht1HiConfAddr + 0x44, Val32);
	mdelay(100);

	for (i=0x200; i>0; i--) {
		if ((Readl(Node2Ht1HiConfAddr + 0x44) & 0x300) == 0) {
			continue;
		} else {
			printf("CRC error found.\r\n");
			printf(" 0x%x\r\n", Readl(Node2Ht1HiConfAddr + 0x44));
			ResetBus(Node2Ht1HiConfAddr);
			goto check_NODE1_HI;
		}
	}
#endif
#endif //defined(QUAD_CPU) && defined(ENABLE_X_LINK)

	ls7a_debug("Dereset Node 0 HT1 bus\r\n");
	Val32 = Readb(Node0Ht13aConfAddr + 0x3e) & (~(u8)0x40);
	Writeb(Node0Ht13aConfAddr + 0x3e, Val32);
	ls7a_debug("after 0x%x\r\n", Readl(Node0Ht13aConfAddr + 0x3c));

#ifdef LS7A_2WAY_CONNECT
	ls7a_debug("Dereset Node 1 HT1 bus\r\n");
	Val32 = Readb(Node1Ht13aConfAddr + 0x3e) & ~0x40;
	Writeb(Node1Ht13aConfAddr + 0x3e, Val32);
	ls7a_debug("after 0x%x\r\n",Readl(Node1Ht13aConfAddr + 0x3c));

	WaitHtBus(WAIT_HT_UP, Node1Ht13aConfAddr);
	ls7a_debug(" 0x%x\r\n", Readl(Node1Ht13aConfAddr + 0x44));
#endif

	WaitHtBus(WAIT_HT_UP, Node0Ht13aConfAddr);
	ls7a_debug(" 0x%x\r\n", Readl(Node0Ht13aConfAddr + 0x44));

	if ((Status = CheckHtLock(Node0Ht13aConfAddr)))
		return Status;

	for (i = 0; i < LinkNodeNum; i++) {
		Ht1CtrlConfAddr = (u64)(HT1_CONTROLLER_CONF_BASE_ADDR | (u64)i << NODE_OFFSET);
		ls7a_debug("Checking Node %lld HT1 CRC error. %llx\r\n", i, Ht1CtrlConfAddr);
		for (j = 0; j < 32; j++) {
			if ((Readl(Ht1CtrlConfAddr + 0x44) & 0x300) == 0) {
				break;
			} else {
				printf("\r\nCRC error found\r\n");
				Val32 = Readl(Ht1CtrlConfAddr + 0x44);
				printf(" 0x%x\r\n",Val32);
				Val32 &= 0xfffffcff;
				Writel(Ht1CtrlConfAddr + 0x44, Val32);
			}
		}

		HtConfAddr = (u64)(HT_CONF_TYPE0_ADDR | (u64)i << NODE_OFFSET);
		ls7a_debug("Checking Bridge HT CRC error bit. %llx\r\n", HtConfAddr);
		for (j = 0; j < 32; j++) {
			Val32 = Readl(HtConfAddr + 0x44);
			ls7a_debug(" 0x%x\r\n",Val32);
			if ((Readl(HtConfAddr + 0x44) & 0x300) == 0) {
				break;
			} else {
				printf("\r\nCRC error found\r\n");
				Val32 = Readl(HtConfAddr + 0x44);
				printf(" 0x%x\r\n",Val32);
				Val32 &= 0xfffffcff;
				Writel(HtConfAddr + 0x44, Val32);
			}
		}
	}

	return 0;
}

void SetBuffNum(u16 Offset, u64 HtConfAddr)
{
	u32 Val32;

	Val32 = Readl(HtConfAddr + Offset);
	Val32 |= 0xfffffff;
	Writel(HtConfAddr + Offset, Val32);
	ls7a_debug("Set buffer num 0x%x\r\n", Readl(HtConfAddr + Offset));
}

void ConfigLs3a3000HtLink(u32 Ht1Cfg, u64 Ht1CtrlConfAddr)
{
	u8 Val8;

	if ((Ht1Cfg & 0x2) == 0)
		Val8 = HT_WIDTH_CTRL_8;
	else
		Val8 = HT_WIDTH_CTRL_16;
	Writeb(Ht1CtrlConfAddr + 0x47, Val8);
	ls7a_debug("Set width 0x%x\r\n", Readl(Ht1CtrlConfAddr + 0x44));

	Writeb(Ht1CtrlConfAddr + LS3A_HT_FREQ + 1, (Ht1Cfg >> 8) & 0xf);
	ls7a_debug("Set Freq 0x%x\r\n", Readl(Ht1CtrlConfAddr + LS3A_HT_FREQ));

	Writel(Ht1CtrlConfAddr + LS3A_HT_PLL_CONF, LS3A_HT1_SOFT_FREQ_CFG);
	ls7a_debug("Set soft config 0x%x\r\n", Readl(Ht1CtrlConfAddr + LS3A_HT_PLL_CONF));

	if (((Ht1Cfg >> 4) & 0xf) == 3) {
		Writel(Ht1CtrlConfAddr + LS3A_HT_REVISION, 0x88600000);
		ls7a_debug("Set GEN3 mode 0x%x\r\n", Readl(Ht1CtrlConfAddr + LS3A_HT_REVISION));
		Writeb(Ht1CtrlConfAddr + LS3A_HT_RETRY_CONTROL, 0x81);
		ls7a_debug("Set retry mode 0x%x\r\n", Readl(Ht1CtrlConfAddr + LS3A_HT_RETRY_CONTROL));
		Writeb(Ht1CtrlConfAddr + LS3A_HT_LINK_TRAIN, 0x78);
		ls7a_debug("Enable scrambling 0x%x\r\n", Readl(Ht1CtrlConfAddr + LS3A_HT_LINK_TRAIN));
	}
}

int ConfigLs7a1000HtLink(u64 Opt)
{
	u64 Ht1CtrlConfAddr;
	u64 HtConfAddr;
	u32 Val32;
	u32 Ht1Cfg;
	int Status;

	Ht1CtrlConfAddr = (u64)(HT1_CONTROLLER_CONF_BASE_ADDR | Opt);
	HtConfAddr = (u64)(HT_CONF_TYPE0_ADDR | Opt);

	Ht1Cfg = ((HT1_HARD_FREQ_CFG << 12) | (HT1_HARD_FREQ_CFG << 8) | (HT1_GEN_CFG << 4) | (HT1_WIDTH_CFG << 1) | (HT1_RECONNECT << 0));

	if ((Status = CheckHtLock(Ht1CtrlConfAddr)))
		return Status;

	ls7a_debug("Wait HT bus be up\n");
	WaitHtBus(WAIT_HT_UP, Ht1CtrlConfAddr);

	ls7a_debug("Set 7A1000 side HT:\r\n");

	if ((Ht1Cfg & 0x1) == 0) {
		ls7a_debug("no reconnet.\r\n");
	} else {
		Val32 = Readl(HtConfAddr + 0x44);
		Val32 &= ~(0xff << 24);

		//Set the link width
		if ((Ht1Cfg & 0x2) == 0) {
			Val32 |= (HT_WIDTH_CTRL_8 << 24);
			ls7a_debug("Set the HT link width as 8bit\n");
		} else {
			Val32 |= (HT_WIDTH_CTRL_16 << 24);
			ls7a_debug("Set the HT link width as 16bit\n");
		}

		Writel(HtConfAddr + 0x44, Val32);
		ls7a_debug("Set link width 0x%x\r\n", Readl(HtConfAddr + 0x44));

		//Set Frequency
		Val32 = Readl(HtConfAddr + 0x4c);
		Val32 &= ~(0xf << 8);
		Val32 |= (((Ht1Cfg >> 12) & 0xf) << 8);
		Writel(HtConfAddr + 0x4c, Val32);
		ls7a_debug("Set Freq 0x%x\r\n", Readl(HtConfAddr + 0x4c));

		//Set soft config
		Val32 = LS7A_VERSION ? LS7A_HT1_SOFT_FREQ_CFG_C : LS7A_HT1_SOFT_FREQ_CFG;
		Writel(HtConfAddr + 0x1f4, Val32);
		ls7a_debug("Set soft config 0x%x\r\n", Readl(HtConfAddr + 0x1f4));

		if (((Ht1Cfg >> 4) & 0xf) == 3) {
			ls7a_debug("Set Gen3 mode\r\n");
			Val32 = Readl(HtConfAddr + 0x6c);
			Val32 &= ~(0xff << 16);
			Val32 |= (0x60 << 16);
			Writel(HtConfAddr + 0x6c, Val32);
			ls7a_debug("Set Gen3 mode 0x%x\r\n", Readl(HtConfAddr + 0x6c));

			Val32 = Readl(HtConfAddr + 0x64);
			Val32 &= ~(0xc1 << 0);
			Val32 |= (0x81 << 0);
			Writel(HtConfAddr + 0x64, Val32);
			ls7a_debug("Set retry mode 0x%x\r\n", Readl(HtConfAddr + 0x64));

			Val32 = Readl(HtConfAddr + 0xd0);
			Val32 |= (0x78 << 0);
			Writel(HtConfAddr + 0xd0, Val32);
			ls7a_debug("Enable scrambling 0x%x\r\n", Readl(HtConfAddr + 0xd0));

			SetBuffNum(0x1dc, HtConfAddr);
		}

		ls7a_debug("Set CPU side HT:\r\n");

		ConfigLs3a3000HtLink(Ht1Cfg, Ht1CtrlConfAddr);

	#ifdef LS3A3000
		SetBuffNum(LS3A_HT_RX_BUFFER_DEFAULT, Ht1CtrlConfAddr);
	#endif
		ls7a_debug("config ht link done.\r\n");
	}
	return 0;
}

//
// Ls7A1000 chipset Font side bus initialize entry point.
//
int FontSideBusInit(int NodeNum)
{
	int Status;
	u64 i;
	int LinkNodeNum = NodeNum;

	ls7a_debug("Ls7A1000 Hyper Transport Bridge Initialize.\n");

#ifdef MULTI_CHIP
#ifdef QUAD_CPU
	LinkNodeNum -= 2; //The upper limit is now 2
#endif
#ifndef LS7A_2WAY_CONNECT
	LinkNodeNum = LinkNodeNum - 1;
#endif
#endif

	for (i = 0; i < LinkNodeNum; i ++)
		if ((Status = ConfigLs7a1000HtLink(i << NODE_OFFSET)))
			return Status;

	if ((Status = ResetHtLink(LinkNodeNum)))
		return Status;

	//disable ht regs
	SET_HT_REG_DISABLE_UEFI(0,0xb);
#ifdef MULTI_CHIP
	SET_HT_REG_DISABLE_UEFI(1,0xb);
#ifdef QUAD_CPU
	SET_HT_REG_DISABLE_UEFI(2,0xf);
	SET_HT_REG_DISABLE_UEFI(3,0xf);
#endif
#endif
#if defined(MULTI_CHIP) && defined(QUAD_CPU) && defined(ENABLE_X_LINK)
	ENABLE_XLINK_UEFI(0);
	ENABLE_XLINK_UEFI(1);
	ENABLE_XLINK_UEFI(2);
	ENABLE_XLINK_UEFI(3);
#endif

	ls7a_debug("LS3A-7A linkup.\n");

	return 0;
}
